/*
 * Copyright 2010 Christoph Widulle (christoph.widulle@googlemail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */



package com.android1.amarena2d.nodes.particle;

import com.android1.amarena2d.commons.BlendFunction;
import com.android1.amarena2d.commons.Callback;
import com.android1.amarena2d.commons.collections.FixedSizeBag;
import com.android1.amarena2d.commons.collections.FixedSizeStack;
import com.android1.amarena2d.engine.GL;
import com.android1.amarena2d.nodes.BaseActionEntity;
import com.android1.amarena2d.nodes.LeafEntity;
import com.android1.amarena2d.nodes.behavior.LifecyleTick;
import com.android1.amarena2d.nodes.particle.effects.ParticleEffect;
import com.badlogic.gdx.graphics.Texture;

public abstract class Emitter<P extends Particle> extends BaseActionEntity implements LifecyleTick, LeafEntity<ParticleSystem> {


    //todo move somewhere else
    public static final class Type {
        public static final int Gravity = 0;
        public static final int Radial = 1;
    }

    public static final class InitFrequency {

        //initParticle() will only be called once on creation. This is ok for infinite duration effects.
        //Effects with a fix duration will always look the same after the emitter has been initialized.
        public static final int OnCreate = 0;

        //initParticle() will be called each time the particle is reused from the pool.
        //Effects will always have a new unqiue look on each start.
        //Needs more cpu time.
        public static final int OnReset = 1;
    }


    protected ParticleSystem particleSystem;
    protected BlendFunction blendFunction;

    protected float emitCounter;
    protected float elapsed;
    protected float duration;

    protected int effectLevel;
    protected int zOrder;

    protected final FixedSizeBag<P> activeParticle;
    protected FixedSizeStack<P> particlePool;

    protected boolean playing;
    protected Callback<Emitter> onFinishCallback;
    protected boolean doLoop;
    protected boolean stopped;


    public Emitter(ParticleEffect effect, int initFrequency, float x, float y) {
        this(effect, initFrequency, x, y, effect.getDefaultWidth(), effect.getDefaultHeight());
    }

    public Emitter(ParticleEffect effect, int effectLevel, float x, float y, float width, float height) {
        this.blendFunction = effect.getBlendFunction();
        this.position.x = x;
        this.position.y = y;
        this.size.width = width;
        this.size.height = height;
        this.duration = effect.getEffectConfig().duration;
        this.effectLevel = effectLevel;

        this.activeParticle = new FixedSizeBag<P>(effect.getEffectConfig().totalParticles);

        timer().scheduleForTick().pause();
    }

    @Override
    public void init() {
        this.particlePool = new FixedSizeStack<P>(createParticles(getParticleEffectConfig().totalParticles));
        super.init();
    }


    public float getPosVarX() {
        return size.width / 2;
    }

    public float getPosVarY() {
        return size.height / 2;
    }

    public Emitter setEffectLevel(int level) {
        this.effectLevel = level;
        return this;
    }


    @Override
    public void render() {
        if (!init || !active || !visible)
            return;

        final Texture texture = particleSystem.managedTexture.getTexture();

        GL.blendFunc(blendFunction);

        //hard to read, but efficient!!
        final int s = activeParticle.size();
        if (s > 0) {
            int found = 0;
            int i = activeParticle.getCapacity();
            while (found != s && i >= 0) {
                Particle p;
                if ((p = activeParticle.get(i)) != null) {
                    found++;
                    if (p.life > 0) {
                        //todo rotation
                        particleSystem.spriteBatch.draw(texture, p.pos.x , p.pos.y , p.size, p.size, p.color);
                    }
                }
                i--;
            }
        }

    }

    public void play() {
        setActive(true);
        reset();
        this.stopped = false;
        this.duration = getParticleEffectConfig().duration;
        playing = true;
        timer().getTickTimer().resume();
    }

    public void play(float duration) {
        setActive(true);
        reset();
        this.stopped = false;
        this.duration = duration;
        playing = true;
        timer().getTickTimer().resume();

    }

    protected void elapsed() {
        playing = false;
    }

    public void stop() {
        playing = false;
        stopped = true;

    }

    protected void finished() {
        if (onFinishCallback != null)
            onFinishCallback.on(this);
        if (doLoop && !stopped)
            play();
        else {
            timer().getTickTimer().pause();
            setActive(false);
        }

    }


    public Emitter setDoLoop(boolean active) {
        this.doLoop = active;
        return this;
    }

    public Emitter setOnFinishCallback(Callback<Emitter> onFinishCallback) {
        this.onFinishCallback = onFinishCallback;
        return this;
    }

    public boolean isPlaying() {
        return playing;
    }

    public void reset() {
        emitCounter = 0;
        elapsed = 0;
        final int s = activeParticle.size();
        if (s > 0) {
            int found = 0;
            int i = activeParticle.getCapacity();
            while (found != s && i >= 0) {
                P p;
                if ((p = activeParticle.get(i)) != null) {
                    found++;
                    particlePool.push(p);
                }
                i--;
            }
            activeParticle.clear();
        }
    }

    public void pause() {
        playing = false;
        timer().getTickTimer().pause();
    }

    public void resume() {
        playing = true;
        timer().getTickTimer().resume();
    }


    @Override
    public void onTick(final float d) {
        if (!visible)
            return;

        final ParticleEffectConfig config = getParticleEffectConfig();

        if (playing && config.emissionRate != 0) {
            float rate = 1.0f / config.emissionRate;
            emitCounter += d;
            while (activeParticle.size() < config.totalParticles && emitCounter > rate) {
                addParticle();
                emitCounter -= rate;
            }

            elapsed += d;
            if (config.duration != -1 && config.duration < elapsed)
                elapsed();
        }

        int totalActive = activeParticle.size();
        if (totalActive > 0) {
            int found = 0;
            int i = activeParticle.getCapacity();
            while (found != totalActive && i >= 0) {
                P p;
                if ((p = activeParticle.get(i)) != null) {
                    found++;
                    if (p.life > 0) {

                        update(p, d);

                    } else {
                        particlePool.push(p);
                        activeParticle.remove(p.id);
                        --totalActive;
                    }
                }
                i--;
            }
        } else if (!playing) {
            finished();
        }
    }

    protected abstract P[] createParticles(int count);

    private boolean addParticle() {

        if (activeParticle.isFull())
            return false;

        P particle = particlePool.pop();

        if (particle != null) {
            if (effectLevel == InitFrequency.OnReset) {
                initParticle(particle);
            }
            resetParticle(particle);
            activeParticle.add(particle.id, particle);
            return true;
        } else
            return false;

    }

    protected abstract void resetParticle(P p);

    protected abstract void initParticle(P p);

    protected abstract void update(P particle, float d);

    public abstract ParticleEffectConfig getParticleEffectConfig();

    @Override
    public void setParent(ParticleSystem parent) {
        this.particleSystem = parent;
    }

    @Override
    public ParticleSystem getParent() {
        return particleSystem;
    }

    @Override
    public int getZOrder() {
        return zOrder;
    }

    @Override
    public void setZOrder(int zOrder) {
        this.zOrder = zOrder;
    }

    @Override
    public float getScaleX() {
        return 1F;
    }

    @Override
    public float getScaleY() {
        return 1F;
    }

    @Override
    public void setScaleX(float x) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void setScaleY(float y) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void setScaleXY(float scale) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void setRotation(float rotation) {
        throw new UnsupportedOperationException();
    }


    @Override
    public float getRotation() {
        return 0;
    }

    @Override
    public void rotateBy(float rotation) {
        throw new UnsupportedOperationException();
    }

}
